package cc.creativecomputing.resonate;

import cc.creativecomputing.CCApp;
import cc.creativecomputing.CCApplicationManager;
import cc.creativecomputing.animation.CCBlendable;
import cc.creativecomputing.events.CCKeyEvent;
import cc.creativecomputing.graphics.CCCamera;
import cc.creativecomputing.graphics.CCDrawMode;
import cc.creativecomputing.graphics.CCGraphics.CCBlendMode;
import cc.creativecomputing.graphics.CCVBOMesh;
import cc.creativecomputing.graphics.shader.CCGLSLShader;
import cc.creativecomputing.graphics.shader.CCShaderBuffer;
import cc.creativecomputing.graphics.shader.imaging.filter.CCSobelFilter;
import cc.creativecomputing.graphics.texture.CCTexture2D;
import cc.creativecomputing.graphics.texture.CCTextureIO;
import cc.creativecomputing.graphics.texture.CCTexture.CCTextureTarget;
import cc.creativecomputing.io.CCIOUtil;
import cc.creativecomputing.math.CCMath;
import cc.creativecomputing.math.CCVector3f;

public class ParticleApp2D extends CCApp{
	
	private CCShaderBuffer _myDataBuffer;
	private CCShaderBuffer _myLastDataBuffer;
	private CCTexture2D    _myEmitTexture;
	
	private CCGLSLShader   _myEmitShader;
	private CCGLSLShader   _myVelocityShader;
	private CCGLSLShader   _myThruShader;
	private CCGLSLShader   _myUpdateShader;
	private CCGLSLShader   _myInitValueShader;
	
	private CCGLSLShader   _myDisplayShader;
	private CCVBOMesh _myMesh;
	private CCCamera  _myCamera;
	
	private int particleW = 256;
	private int particleH = 256;
	
	@Override
	public void setup() {
		
		_myCamera = new CCCamera(g);
		_myCamera.position().x = 0;
		_myCamera.position().y = 0;
		_myCamera.position().z =150;
		_myCamera.target(new CCVector3f(0,0,0));
		
		_myEmitShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader/texture2D/emit_texture2D_fp.glsl"));
		_myEmitShader.load();
		
		_myDisplayShader = new CCGLSLShader (CCIOUtil.classPath(this, "shader/texture2D/display_vp.glsl"), 
				  							 CCIOUtil.classPath(this, "shader/texture2D/display_fp.glsl"));
		_myDisplayShader.load();
		
		_myVelocityShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader/texture2D/force_2D_fp.glsl"));
		_myVelocityShader.load();
		
		_myThruShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader/texture2D/thru_fp.glsl"));
		_myThruShader.load();

		_myUpdateShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader/texture2D/update_constraints_fp.glsl"));
		_myUpdateShader.load();
		
		_myInitValueShader = new CCGLSLShader (null, CCIOUtil.classPath(this, "shader/texture2D/init_fp.glsl"));
		_myInitValueShader.load();
		
		_myMesh = new CCVBOMesh(CCDrawMode.POINTS, particleW*particleH);
		for (int i=0; i<particleW; i++) {
			for (int j=0; j<particleH; j++) {
				_myMesh.addVertex((float)i/particleW, (float)j/particleH);
			}
		}
		
		_myDataBuffer = new CCShaderBuffer (32, 3, 4, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myDataBuffer.clear();
		_myLastDataBuffer = new CCShaderBuffer (32, 3, 4, particleW, particleH, CCTextureTarget.TEXTURE_2D);
		_myLastDataBuffer.clear();
		
		g.pointSize(1);
		g.noBlend();
	}

	private void swap() {
		CCShaderBuffer tmp = _myDataBuffer;
		_myDataBuffer = _myLastDataBuffer;
		_myLastDataBuffer = tmp;
	}
	
	
	@Override
	public void update (float theDeltaTime) {
	
		_myEmitShader.start();

		//g.texture (0, _myEmitTexture);
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		g.texture (3, _myEmitTexture);
		
		//_myEmitShader.uniform1i("emitTexture", 0);
		_myEmitShader.uniform1i("positions", 0);
		_myEmitShader.uniform1i("velocities", 1);
		_myEmitShader.uniform1i("infos", 2);
		_myEmitShader.uniform1i("emitTexture", 3);
		_myEmitShader.uniform3f("randomSeed", CCMath.random(100f), CCMath.random(100f),  CCMath.random(3000,10000));
		
		_myDataBuffer.draw();
		_myEmitShader.end();
		g.noTexture();
		
		swap();
		
		
		_myThruShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		_myThruShader.uniform1i("positions", 0);
		_myThruShader.uniform1i("velocities", 1);
		_myThruShader.uniform1i("infos", 2);
		
		_myDataBuffer.draw();
		_myThruShader.end();
		g.noTexture();
		
		// todo add precalculated force texture here
		_myVelocityShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
	
		_myVelocityShader.uniform1i("positions", 0);
		_myVelocityShader.uniform1i("velocities", 1);
		_myVelocityShader.uniform1i("forceTexture", 2);

		_myVelocityShader.uniform1i("dataW", width);
		_myVelocityShader.uniform1i("dataH", height);
		
		_myDataBuffer.draw(1);
		_myVelocityShader.end();
		g.noTexture();
		
		swap();
		
		_myUpdateShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		g.texture (1, _myLastDataBuffer.attachment(1));
		g.texture (2, _myLastDataBuffer.attachment(2));
		
		_myUpdateShader.uniform1i("positions", 0);
		_myUpdateShader.uniform1i("velocities", 1);
		_myUpdateShader.uniform1i("infos", 2);
		_myUpdateShader.uniform1i("constraints", 3);
		
		_myUpdateShader.uniform1f("deltaTime", theDeltaTime);
		
		_myDataBuffer.draw();
		_myUpdateShader.end();
		g.noTexture();
		
		swap();
	}
	
	private void updateCam() {
		_myCamera.near(0.1f);
		_myCamera.far(1000f);
			
		_myCamera.xRotation(0);
		_myCamera.yRotation(0);
		_myCamera.zRotation(0);
			
		_myCamera.fov(45);
	}
	
	@Override
	public void draw() {
		
		updateCam();
		_myCamera.draw(g);
		
		g.pushAttribute();
		g.clearColor(0f);
		g.clear();
		g.pointSize(1);

		_myDisplayShader.start();
		g.texture (0, _myLastDataBuffer.attachment(0));
		_myDisplayShader.uniform1i("positions", 0);
		_myMesh.draw(g);
		_myDisplayShader.end();
		g.noTexture();
		g.popAttribute();
	
	}
	
	public static void main (String[] args) {
		CCApplicationManager myManager = new CCApplicationManager(ParticleApp2D.class);
		myManager.settings().size(1000, 1000);
		myManager.settings().display(0);
		myManager.settings().antialiasing(8);
		myManager.settings().fov(30);
		myManager.settings().antialiasing(10);
		myManager.start();
	}
}
